//
//  ConfigPrivateParamsViewController.swift
//  Hermes
//
//  Created by FH on 2021/2/17.
//  Copyright © 2021 Agora. All rights reserved.
//

import Foundation

protocol EditPrivateParamsContext {
    var selectedParam: PropState<AstNode?> { get }
    func saveEditParam(_ key: String, _ item: AstNode)
}

extension ConfigPrivateParamsViewController: SearchResultContext {
    var dataSource: PropState<[AstNode]> {
        return self.$searchDataSource
    }

    func selectItem(_ item: AstNode) {
        self.$selectedSearchItem.update(value: item, localStates)
    }
}

extension ConfigPrivateParamsViewController: EditPrivateParamsContext {
    var selectedParam: PropState<AstNode?> {
        return self.$selectedSearchItem
    }

    func saveEditParam(_ key: String, _ item: AstNode) {

    }
}

// REMARK: edit compnent

class EditPrivateParamsComponent {
    // just for get private-params default value
    static let agora = AgoraRtcEngineKit.sharedEngine(withAppId: "", delegate: nil)
    let allEnumTypes: [String : AstNode]
    let currYoga = FLContext()
    let properties: EditPrivateParamsContext
    var tableParams: FLTableView<AstNode, SearchResultRowCellView>! = nil
    let scrollEditor: NSScrollView = NSScrollView()
    var editorContent: NSView! = nil
    let lblParamJsonType = FLLabel()
    let lblParamCppType = FLLabel()
    let inputParamKey = FLInputField()
    let inputParamValue = FLTextField()
    var customInputsPanel: NSView! = nil
    var customInputControls = [NSView]()
    var btnAddMoreInput: NSButton! = nil
    // inner states
    var isInited = false
    var startEditing = false
    var defaultValue = ""
    var currEditingParam: AstNode? = nil
    var mappingEnumNode: AstNode? = nil
    var useSuggestInputType = PrivateParamJSONTipsType.Raw
    var objectRelativeRoot: Int = 0
    // objectSelectTypes => (currObjectLayer, div, span-1, superView, btnAddMore)
    var mappingObjectLayer = [NSPopUpButton : (Int, NSView, NSView, NSView, NSButton?)]()
    var insertObjectInputsIndex: Int? = nil
    
    init(_ properties: EditPrivateParamsContext) {
        self.properties = properties
        self.allEnumTypes = HermesContext().privateParams.getCppPrivateParamsEnumCases()
        self.dataBinding()
    }
    
    func viewWillAppear() { }
       
    func viewDidAppear() {
        let windows = NSApplication.shared.windows
        if !self.isInited && windows.count > 1, let lastWindow = windows.last {
            self.isInited = true
            NotificationCenter.default.rac_addObserver(notifyName: FLNotify_OnFocus_Key, object: lastWindow)
                .subscribeNext {[weak self] notify in
                    if let self = self, let userInfo = notify.userInfo, let sender = userInfo["sender"] as? NSView {
                        if !self.startEditing {
                            // handle inputParamValue text
                            if self.inputParamValue.isEqual(sender) {
                                self.inputParamValue.flText = ""
                                self.inputParamValue.textColor = .black
                            } else {
                                self.inputParamValue.flText = self.defaultValue
                                self.inputParamValue.textColor = .lightGray
                            }
                        }
                    }
                }
        }
    }
    
    func layoutViews(yoga: FLContext, _ superView: NSView) {
        let bgView = NSView()
        bgView.bind(to: yoga, superView) { it in
            it.flexGrow = 1
        }
        // TODO; remove
        let nodes = HermesContext().privateParams.getCppPrivateParams()
        
        self.tableParams = buildRowCellTableView(yoga, SearchResultRowCellView.self, data: nodes)
        self.tableParams.bind(to: yoga, bgView) { it in
            it.flexGrow = 1
        }
        
        self.scrollEditor.bind(to: yoga, bgView) { it in
            it.flexGrow = 2
        }
        
        // startObservrOnFocusEvent
        self.tableParams.startObservrOnFocusEvent()
        self.inputParamKey.startObservrOnFocusEvent()
        self.inputParamValue.startObservrOnFocusEvent()
    }
    
    private func dataBinding() {
        self.inputParamValue.rac_valueDidChanged.subscribeNext {[weak self] _ in
            self?.startEditing = true
        }
        
        self.properties.selectedParam.signal().ignoreNilValue().subscribeNext {[weak self] item in
            if let self = self, let item = item, (self.currEditingParam == nil || self.currEditingParam! != item) {
                self.currEditingParam = item
                // param type
                let jsonType = item.cppPrivateParamJsonTypeTips
                self.lblParamJsonType.flText = "JSON Type:  \(jsonType)"
                self.lblParamCppType.flText = "C++ Type:    \(item.cppPrivateParamType)"
                // param key
                let key = item.cppPrivateParamKey
                self.inputParamKey.flText = key
                // param value
                self.defaultValue = item.getCppPrivateParamDefaultValue(with: Self.agora.getParameter(key, args: nil))
                // find mapping enum types
                self.mappingEnumNode = item.getCppPrivateParamEnumValueMappingType(&self.defaultValue, self.allEnumTypes)
                self.inputParamValue.flText = self.defaultValue
                self.inputParamValue.textColor = .lightGray
                // layout edit panel
                self.layoutEditPanel(item, self.scrollEditor.bounds)
            }
        }
    }
    
    private func layoutEditPanel(_ item: AstNode, _ contentBounds: NSRect) {
        let contentWidth = (contentBounds.size.width - 8).toFLValue()
        if self.editorContent == nil {
            self.editorContent = NSView()
            self.editorContent.bindAsRoot(to: currYoga) { it in
                it.width = contentBounds.size.width.toFLValue()
                it.minHeight = contentBounds.size.height.toFLValue()
                it.alignItems = .center
            }
            self.editorContent.flBackgroundColor = .lightGray
            self.scrollEditor.contentView.documentView = self.editorContent
            
            self.inputParamKey.bind(to: currYoga, self.editorContent) { it in
                it.width = contentWidth
                it.height = 32
                it.marginTop = 4
                it.marginBottom = 8
            }
            self.inputParamKey.font = NSFont.systemFont(ofSize: 15)
            
            self.lblParamJsonType.bind(to: currYoga, self.editorContent) { it in
                it.height = 20
                it.width = contentWidth
            }
            self.lblParamJsonType.font = NSFont.systemFont(ofSize: 13)

            self.lblParamCppType.bind(to: currYoga, self.editorContent) { it in
                it.marginTop = 2
                it.height = 20
                it.width = contentWidth
            }
            self.lblParamCppType.font = NSFont.systemFont(ofSize: 13)

            self.inputParamValue.bind(to: currYoga, self.editorContent) { it in
                it.height = 60
                it.width = contentWidth
            }
            self.inputParamValue.font = NSFont.systemFont(ofSize: 13)

            currYoga.apply()
        }
        self.layoutCustomInputsPanel(contentBounds.size.width)
    }
    
    private func layoutCustomInputsPanel(_ contentWidth: CGFloat) {
        if self.customInputsPanel != nil {
            currYoga.removeNode(by: self.customInputsPanel)
            self.startEditing = false
            self.customInputControls.removeAll()
            self.useSuggestInputType = .Raw
            self.objectRelativeRoot = 0
            self.mappingObjectLayer.removeAll()
            self.insertObjectInputsIndex = nil
        }
        self.customInputsPanel = NSView()
        self.customInputsPanel.bind(to: currYoga, self.editorContent) { it in
            it.padding = 8
            it.width = contentWidth.toFLValue()
        }
        // [category, type1, type2, ... ]
        let jsonTypes = self.currEditingParam!.getCppPrivateParamJsonTypes()
        let category = jsonTypes.first!
        let isJsonObject = category == .Object
        let isJsonList = category == .List
        let needSuggest = (isJsonObject || isJsonList) || (jsonTypes.last! == .Boolean || self.mappingEnumNode != nil)
        self.addSuggestInputsControls(category, Array(jsonTypes.dropFirst()), needSuggest, self.customInputsPanel)
        currYoga.apply()
    }
    
    // REMARK: add suggest inputs
    private func addSuggestInputsControls(_ category: PrivateParamJSONTipsType,
                                          _ types: [PrivateParamJSONTipsType],
                                          _ needSuggest: Bool,
                                          _ superView: NSView,
                                          valueJsonObject: [String : Any]?=nil) {
        var showSuggest = needSuggest
        var jsonObject: [String : Any]? = valueJsonObject
        // parse `String` type json object
        if valueJsonObject == nil && self.objectRelativeRoot == 0 {
            let needParseJsonObject = category == .Object || (category == .Raw && types.first! == .String)
            if needParseJsonObject && self.defaultValue.count > 0, let json = self.defaultValue.toJSONObject() {
                jsonObject = json
                if !showSuggest {
                    showSuggest = true
                }
            }
            let showSelectSuggest = !showSuggest && (category == .Raw && types.first! == .String)

            let span = NSView()
            span.bind(to: currYoga, self.customInputsPanel) { it in
                it.direction = .row
                it.marginTop = 12
                it.marginBottom = 8
            }
            
            let selectSuggest = NSPopUpButton()
            selectSuggest.bind(to: currYoga, span) { it in
                it.width = 210
                it.height = 20
                it.display = showSelectSuggest ? .flex : .none
            }
            selectSuggest.addItems(withTitles: ["Suggest Inputs", "Use Object Inputs", "Use List Inputs"])
            
            let lblSuggest = FLLabel()
            lblSuggest.bind(to: currYoga, span) { it in
                it.width = 120
                it.height = 20
                it.display = showSelectSuggest ? .none : .flex
            }
            lblSuggest.font = NSFont.systemFont(ofSize: 14)
            lblSuggest.flText = "Suggest Inputs: "
            lblSuggest.flBackgroundColor = .lightGray
            lblSuggest.isHidden = !showSuggest
            // save curr param value
            let btnSuggest = NSButton()
            btnSuggest.bind(to: currYoga, span) { it in
                it.width = 90
                it.height = 20
                it.display = showSelectSuggest ? .none : .flex
            }
            btnSuggest.flTitle = "Use Suggest"
            btnSuggest.flTitleColor = .darkGray
            btnSuggest.flBackgroundColor = .white
            btnSuggest.flCornerRadius = 4
            btnSuggest.rac_eventTouch.subscribeNext {[weak self] _ in
                if let self = self {
                    self.startEditing = true
                    self.inputParamValue.textColor = .black
                    self.onUseSuggestInputs()
                }
            }
            btnSuggest.isHidden = !showSuggest
            
            let btnSave = NSButton()
            btnSave.bind(to: currYoga, span) { it in
                it.width = 90
                it.height = 20
                it.marginLeft = 12
            }
            btnSave.flTitle = "Save Value"
            btnSave.flTitleColor = .darkGray
            btnSave.flBackgroundColor = .white
            btnSave.flCornerRadius = 4
            btnSave.rac_eventTouch.subscribeNext {[weak self] _ in
                if let self = self {
                    self.onSaveParamValue()
                }
            }
            // bind selectSuggest events
            selectSuggest.rac_eventTouch.subscribeNext {[weak self, weak lblSuggest, weak btnSuggest] select in
                if let self = self, let lblSuggest = lblSuggest, let btnSuggest = btnSuggest {
                    if select.indexOfSelectedItem == 1 {
                        self.addSuggestInputsControls(.Object, [], false, self.customInputsPanel, valueJsonObject: [:])
                    } else if select.indexOfSelectedItem == 2 {
                        self.addSuggestInputsControls(.List, [.String], false, self.customInputsPanel, valueJsonObject: [:])
                    }
                    if let suggestNode = self.currYoga.getNode(by: select) {
                        suggestNode.display = .none
                    }
                    if let lblSuggestNode = self.currYoga.getNode(by: lblSuggest) {
                        lblSuggestNode.display = .flex
                    }
                    if let btnSuggestNode = self.currYoga.getNode(by: btnSuggest) {
                        btnSuggestNode.display = .flex
                    }
                    self.currYoga.apply()
                    lblSuggest.isHidden = false
                    btnSuggest.isHidden = false
                }
            }
        }
        // handle Raw/Object/List suggest types
        if category == .Object || (jsonObject != nil && category == .Raw) {
            if let jsonObject = jsonObject, jsonObject.count > 0 {
                for (key, value) in jsonObject {
                    if let subJsonObject = value as? [String : Any] {
                        self.addInputsControl(by: .ObjectItem(key), itemType: .Object, defaultValue: "",
                                              jsonObject: subJsonObject, superView, 4, true)
                    } else if !(value is NSArray) {
                        var valueStr = "\(value)"
                        if let boolValue = value as? Bool {
                            valueStr = "\(boolValue)"
                        }
                        self.addInputsControl(by: .ObjectItem(key), defaultValue: valueStr, superView, 4, true)
                    }
                }
            } else {
                // default Key-Value
                self.addInputsControl(by: .ObjectItem(""), defaultValue: "", superView, 4, true)
            }
            self.useSuggestInputType = .Object
        } else if category == .List {
            let defaultValue = self.defaultValue.replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "")
            let split = defaultValue.split(separator: ",")
            // for tuple, like [Number, Boolean]
            if types.count > 1 {
                for i in 0..<types.count {
                    var type = PrivateParamJSONTipsType.String
                    var value = ""
                    var valueJson: [String : Any]? = nil
                    if split.count > i {
                        type = types[i]
                        value = split[i].trimmingCharacters(in: .whitespaces)
                        // nest object value
                        if type == .Object {
                            valueJson = value.toJSONObject()
                            if let valueJson = valueJson {
                                self.addObjectLayerNumber(nil)
                                self.addSuggestInputsControls(.Object, [], false, superView, valueJsonObject: valueJson)
                                continue
                            }
                        }
                    }
                    self.addInputsControl(by: type, defaultValue: value, superView, 4, false)
                }
            } else // for list, like [String]
            if types.count == 1 {
                let type = types[0]
                if split.count > 0 {
                    for i in 0..<split.count {
                        let value = String(split[i])
                        self.addInputsControl(by: type, defaultValue: value, superView, 4, i + 1 == split.count)
                    }
                } else {
                    // default input
                    self.addInputsControl(by: type, defaultValue: "", superView, 4, true)
                }
            }
            self.useSuggestInputType = .List
        } else {
            if let type = types.first {
                if type == .Boolean {
                    self.addInputsControl(by: .Boolean, defaultValue: self.defaultValue, superView, 4, false)
                } else if type == .Number && self.mappingEnumNode != nil {
                    self.addInputsControl(by: .Number, defaultValue: self.defaultValue, superView, 4, false)
                }
            }
        }
    }

    // tags: 1001 = selectBool、1002 = selectEnum、1003 = input
    //       2001 = inputItemKey、2002 = selectItemValueTypes、2003 = inputItemValue、
    //       2004 = btnAddMoreObjectItem、2005 = btnRemoveObjectItem
    private func addInputsControl(by type: PrivateParamJSONTipsType,
                                  itemType: PrivateParamJSONTipsType = .String, // or .Object
                                  defaultValue: String,
                                  jsonObject: [String : Any]?=nil,
                                  _ superView: NSView,
                                  _ marginTop: Float,
                                  _ needAddMore: Bool) {
        if type == .Boolean {
            let selectBool = NSPopUpButton()
            selectBool.bind(to: currYoga, superView) { it in
                it.width = 300
                it.height = 26
                if marginTop > 0 {
                    it.marginTop = marginTop.toFLValue()
                }
            }
            selectBool.flTag = 1001
            selectBool.addItems(withTitles: ["true", "false"])
            selectBool.font = NSFont.systemFont(ofSize: 14)
            if defaultValue == "false" {
                selectBool.selectItem(at: 1)
            }
            self.customInputControls.append(selectBool)
        } else if type == .Number, let enumNode = self.mappingEnumNode {
            let selectEnum = NSPopUpButton()
            selectEnum.bind(to: currYoga, superView) { it in
                it.width = 300
                it.height = 26
                if marginTop > 0 {
                    it.marginTop = marginTop.toFLValue()
                }
            }
            selectEnum.flTag = 1002
            let enumCases = enumNode.children.map { $0.name }
            let items = enumNode.children.map { "\($0.name) (\($0.value?.value ?? "NaN"))" }
            selectEnum.addItems(withTitles: items)
            // parse default enumCase
            let caseItem = defaultValue
            if let findIndex = enumCases.firstIndex(where: { caseItem.contains($0) }) {
                selectEnum.selectItem(at: findIndex)
            }
            self.customInputControls.append(selectEnum)
        } else if case let .ObjectItem(key) = type {
            let div = NSView()
            div.bind(to: currYoga, superView) { it in
                if marginTop > 0 {
                    it.marginTop = marginTop.toFLValue()
                }
                if self.objectRelativeRoot > 0 {
                    it.marginLeft = 4
                }
            }
            // key
            let span1 = NSView()
            span1.bind(to: currYoga, div) { it in
                it.direction = .row
                it.alignItems = .center
            }
            let lblLevel = FLLabel()
            lblLevel.bind(to: currYoga, span1) { it in
                it.marginLeft = 4
                it.width = 46
                it.height = 16
            }
            lblLevel.font = NSFont.systemFont(ofSize: 11)
            lblLevel.textColor = (self.objectRelativeRoot % 2 == 0) ? .darkGray : .white
            lblLevel.flText = "layer_\(self.objectRelativeRoot)"
            
            let lblKey = FLLabel()
            lblKey.bind(to: currYoga, span1) { it in
                it.width = 70
                it.height = 18
            }
            lblKey.font = NSFont.systemFont(ofSize: 13)
            lblKey.textColor = .black
            lblKey.flText = "Key: "
            
            let inputKey = FLInputField()
            inputKey.bind(to: currYoga, span1) { it in
                it.marginLeft = 4
                it.width = 160
                it.height = 26
            }
            inputKey.flTag = 2001
            inputKey.font = NSFont.systemFont(ofSize: 12)
            inputKey.textColor = .black
            inputKey.flText = key
            inputKey.startObservrOnFocusEvent()
            if let index = self.insertObjectInputsIndex {
                self.customInputControls.insert(inputKey, at: index)
                self.insertObjectInputsIndex = index + 1
            } else {
                self.customInputControls.append(inputKey)
            }
            // value
            let span2 = NSView()
            span2.bind(to: currYoga, div) { it in
                it.marginTop = 2
                it.direction = .row
                it.alignItems = .center
            }
            let selectTypes = NSPopUpButton()
            selectTypes.bind(to: currYoga, span2) { it in
                it.width = 120
                it.height = 20
            }
            selectTypes.flTag = 2002
            selectTypes.addItems(withTitles: ["Value: string", "Value: object"])
            if let index = self.insertObjectInputsIndex {
                self.customInputControls.insert(selectTypes, at: index)
                self.insertObjectInputsIndex = index + 1
            } else {
                self.customInputControls.append(selectTypes)
            }
            
            let inputValue = FLTextField()
            inputValue.bind(to: currYoga, span2) { it in
                it.marginLeft = 4
                it.width = 160
                it.height = 36
            }
            inputValue.flTag = 2003
            inputValue.font = NSFont.systemFont(ofSize: 12)
            inputValue.textColor = .black
            inputValue.flText = defaultValue
            inputValue.startObservrOnFocusEvent()
            if let index = self.insertObjectInputsIndex {
                self.customInputControls.insert(inputValue, at: index)
                self.insertObjectInputsIndex = index + 1
            } else {
                self.customInputControls.append(inputValue)
            }
            
            let btnRemoveItem = NSButton()
            btnRemoveItem.bind(to: currYoga, span2) { it in
                it.width = 18
                it.height = 18
                it.marginLeft = 8
            }
            btnRemoveItem.flTag = 2005
            btnRemoveItem.flTitleColor = .white
            btnRemoveItem.flBackgroundColor = .darkGray
            btnRemoveItem.font = NSFont.systemFont(ofSize: 16)
            btnRemoveItem.flCornerRadius = 9
            btnRemoveItem.flTitle = "-"
            let objectRelativeRoot = self.objectRelativeRoot
            // object-value
            if itemType == .Object {
                // select object-value
                selectTypes.selectItem(at: 1)
                self.onObjectSelectTypesClick(byManual: false, jsonObject: jsonObject, selectTypes, inputValue, btnRemoveItem, div)
            }
            // bind btnRemoveItem event
            btnRemoveItem.rac_eventTouch.subscribeNext {
                [weak self, weak div, weak superView, weak selectTypes] _ in
                if let self = self, let div = div, let superView = superView, let selectTypes = selectTypes {
                    if let currItem = self.mappingObjectLayer.first(where: { (_, info) in info.1 == div && info.3 == superView }) {
                        var firstPreItemKeyIndex: Int = self.customInputControls.count
                        var lastPreItemKeyIndex: Int = -1
                        var currItemKeyIndex: Int = -1
                        var secondItemKeyIndex: Int = 0
                        for i in 0..<self.customInputControls.count {
                            let view = self.customInputControls[i]
                            if let select = view as? NSPopUpButton, select.flTag == 2002, let info = self.mappingObjectLayer[select],
                               info.0 == currItem.value.0 && info.3 == superView {
                                if select != selectTypes {
                                    if i > lastPreItemKeyIndex {
                                        lastPreItemKeyIndex = i
                                    }
                                } else {
                                    currItemKeyIndex = i
                                }
                                if i < firstPreItemKeyIndex {
                                    firstPreItemKeyIndex = i
                                } else if secondItemKeyIndex == 0 {
                                    secondItemKeyIndex = i
                                }
                            }
                        }
                        // check is not last item in sibling items
                        if lastPreItemKeyIndex > -1 {
                            // handle scope level tip
                            if firstPreItemKeyIndex == currItemKeyIndex && secondItemKeyIndex != 0,
                               let secondItemKey = self.customInputControls[secondItemKeyIndex] as? NSPopUpButton,
                               let secondItemInfo = self.mappingObjectLayer[secondItemKey] {
                                for subView in secondItemInfo.2.subviews {
                                    if let lblLevel = subView as? NSTextField, lblLevel.flText.hasSuffix("|") {
                                        lblLevel.flText = "layer_\(secondItemInfo.0)"
                                        break
                                    }
                                }
                            }
                            // move curr btnAddMore to preItem
                            if currItem.value.4 != nil,
                               let preItemKey = self.customInputControls[lastPreItemKeyIndex] as? NSPopUpButton,
                               let preItemInfo = self.mappingObjectLayer[preItemKey] {
                                
                                self.layoutBtnAddMore(preItemInfo.2, false, preItemKey)
                            }
                            // remove controls
                            for _ in 0..<3 {
                                self.customInputControls.remove(at: currItemKeyIndex-1)
                            }
                            self.mappingObjectLayer.removeValue(forKey: currItem.key)
                            self.currYoga.removeNode(by: div)
                            self.currYoga.apply()
                        }
                    }
                }
            }
            // bind selectValue event
            selectTypes.rac_eventTouch.subscribeNext {
                [weak self, weak inputValue, weak div, weak btnRemoveItem] select in
                if let self = self, let inputValue = inputValue, let div = div, let btnRemoveItem = btnRemoveItem {
                    self.onObjectSelectTypesClick(byManual: true, jsonObject: nil, select, inputValue, btnRemoveItem, div)
                }
            }
            // check show level tip
            for (_, info) in self.mappingObjectLayer {
                if info.0 == self.objectRelativeRoot && info.3 == superView {
                    lblLevel.flText = "\(String(repeating: "-", count: self.objectRelativeRoot))|"
                    break
                }
            }
            // save objectSelectTypes
            var findBtnAddMore: NSButton? = nil
            for (_, info) in self.mappingObjectLayer {
                if info.0 == self.objectRelativeRoot && info.3 == superView, let btn = info.4 {
                    findBtnAddMore = btn
                    break
                }
            }
            self.mappingObjectLayer[selectTypes] = (objectRelativeRoot, div, span1, superView, findBtnAddMore)
            for (key, info) in self.mappingObjectLayer {
                // set sibling item's btnAddMore = nil
                if key != selectTypes && info.0 == self.objectRelativeRoot && info.3 == superView,
                   let btn = info.4, btn == findBtnAddMore {
                    self.mappingObjectLayer[key] = (info.0, info.1, info.2, info.3, nil)
                }
            }
            // reLayout btnAddMore
            if needAddMore {
                self.layoutBtnAddMore(span1, false, selectTypes)
            }
        } else {
            let span = NSView()
            span.bind(to: currYoga, superView) { it in
                it.direction = .row
                it.alignItems = .center
                if marginTop > 0 {
                    it.marginTop = marginTop.toFLValue()
                }
            }
            let input = FLInputField()
            input.bind(to: currYoga, span) { it in
                it.width = 300
                it.height = 26
            }
            input.flTag = 1003
            input.textColor = .black
            input.font = NSFont.systemFont(ofSize: 12)
            input.flText = defaultValue
            self.customInputControls.append(input)
            // add more button
            if needAddMore {
                self.layoutBtnAddMore(span, true)
            }
        }
    }
    
    private func layoutBtnAddMore(_ superView: NSView, _ useInListType: Bool, _ objectSelectTypes: NSPopUpButton?=nil) {
        var btnAddMoreInput: NSButton! = useInListType ? self.btnAddMoreInput : self.mappingObjectLayer[objectSelectTypes!]?.4
        if btnAddMoreInput == nil {
            if useInListType {
                self.btnAddMoreInput = NSButton()
                btnAddMoreInput = self.btnAddMoreInput
            } else {
                btnAddMoreInput = NSButton()
                btnAddMoreInput.tag = 2004
                if let info = self.mappingObjectLayer[objectSelectTypes!] {
                    self.mappingObjectLayer[objectSelectTypes!] = (info.0, info.1, info.2, info.3, btnAddMoreInput)
                }
            }
            btnAddMoreInput.rac_eventTouch.subscribeNext {[weak self] btn in
                if let self = self {
                    if useInListType {
                        self.addInputsControl(by: .String, defaultValue: "", self.customInputsPanel, 4, true)
                    } else {
                        for (select, info) in self.mappingObjectLayer {
                            if let btnOfInfo = info.4, btnOfInfo == btn {
                                // check insert or append inputs
                                self.findObjectInsertIndex(select, addForLayer: info.0)
                                // find superView and add more
                                self.objectRelativeRoot = info.0
                                self.addInputsControl(by: .ObjectItem(""), defaultValue: "", info.3, 4, true)
                                break
                            }
                        }
                    }
                    self.currYoga.apply()
                    self.scrollEditor.scrollToBottom(forVertical: true)
                }
            }
        } else {
            currYoga.removeNode(by: btnAddMoreInput)
        }
        btnAddMoreInput.bind(to: currYoga, superView) { it in
            it.width = 18
            it.height = 18
            it.marginLeft = 8
        }
        btnAddMoreInput.flTitleColor = .darkGray
        btnAddMoreInput.flBackgroundColor = .white
        btnAddMoreInput.font = NSFont.systemFont(ofSize: 16)
        btnAddMoreInput.flCornerRadius = 9
        btnAddMoreInput.flTitle = "+"
    }
    
    private func findObjectInsertIndex(_ select: NSPopUpButton, addForLayer: Int?) {
        if let currLayer = addForLayer {
            var selectIndex = 0
            for i in 0..<self.customInputControls.count {
                let item = self.customInputControls[i]
                if item.flTag == 2002, let select = item as? NSPopUpButton,
                   let info = self.mappingObjectLayer[select], currLayer <= info.0 {
                    selectIndex = i
                }
            }
            self.insertObjectInputsIndex = selectIndex + 2
        } else if let selectIndex = self.customInputControls.firstIndex(where: { $0 == select }),
           selectIndex + 2 < self.customInputControls.count {
            self.insertObjectInputsIndex = selectIndex + 2
        } else {
            self.insertObjectInputsIndex = nil
        }
    }
    
    private func addObjectLayerNumber(_ objectSelectTypes: NSPopUpButton?) {
        if let select = objectSelectTypes, let info = self.mappingObjectLayer[select] {
            self.objectRelativeRoot = info.0 + 1
        } else {
            self.objectRelativeRoot += 1
        }
    }
    
    // REMARK: handle save param value
    
    private func onSaveParamValue() {
        
    }
    
    private func onUseSuggestInputs() {
        let jsonTypes = self.currEditingParam!.getCppPrivateParamJsonTypes()
        if let category = jsonTypes.first {
            let types = Array(jsonTypes.dropFirst())
            if self.useSuggestInputType == .Object {
                var object = "{"
                var parentLayer = -1
                var currLayer = 0
                var currKey = ""
                var relativeRoot: String = ""
                for i in 0..<self.customInputControls.count {
                    let inputControl = self.customInputControls[i]
                    if inputControl.flTag == 2001 {
                        let key = self.handleSuggestInputValue(.String, inputControl) ?? ""
                        currKey = "\(key): "
                    } else if inputControl.flTag == 2002 {
                        relativeRoot = self.handleSuggestInputValue(.String, inputControl)!
                    } else if inputControl.flTag == 2003 {
                        let split = relativeRoot.components(separatedBy: "-")
                        if let layer = Int(split[1]) {
                            if layer < currLayer {
                                object = object.trimmingCharacters(in: .init(arrayLiteral: ",", " ")).appending("}, ")
                                currLayer = layer
                                parentLayer = currLayer - 1
                            }
                        }
                        if split[0] == "object" {
                            object.append("\(currKey){")
                            parentLayer += 1
                            currLayer += 1
                        } else {
                            let value = self.handleSuggestInputValue(.String, inputControl) ?? ""
                            object.append("\(currKey)\(value), ")
                        }
                    }
                }
                while currLayer > -1 {
                    object = object.trimmingCharacters(in: .init(arrayLiteral: ",", " ")).appending("}")
                    currLayer -= 1
                }
                self.inputParamValue.flText = object
                if category == .Raw && types.first! == .String {
                    self.inputParamValue.flText = "\"\(self.inputParamValue.flText)\""
                }
            } else if self.useSuggestInputType == .List {
                var list = [String]()
                if types.count > 1 {
                    for i in 0..<self.customInputControls.count {
                        let type = types[i]
                        if type == .Object {
                            
                        } else {
                            let inputControl = self.customInputControls[i]
                            if let value = self.handleSuggestInputValue(type, inputControl) {
                                list.append(value)
                            }
                        }
                    }
                } else {
                    for i in 0..<self.customInputControls.count {
                        let inputControl = self.customInputControls[i]
                        if let value = self.handleSuggestInputValue(.String, inputControl) {
                            list.append(value)
                        }
                    }
                }
                if list.count > 0 {
                    self.inputParamValue.flText = "[\(list.joined(separator: ", "))]"
                } else {
                    self.inputParamValue.flText = "[]"
                }
                if category == .Raw && types.first! == .String {
                    self.inputParamValue.flText = "\"\(self.inputParamValue.flText)\""
                }
            } else {
                if let value = self.handleSuggestInputValue(types.first!, self.customInputControls.first!) {
                    self.inputParamValue.flText = value
                }
            }
        }
    }
    
    private func handleSuggestInputValue(_ type: PrivateParamJSONTipsType, _ inputControl: NSView) -> String? {
        if let input = inputControl as? FLTextField {
            let value = input.flText.trimmingCharacters(in: .init(arrayLiteral: "\""))
            if type == .Boolean {
                return value.isNumber() ? value : "NaN"
            } else if type == .Number {
                return value.isBoolean() ? value : "NaN"
            } else {
                if value.count > 0 {
                    return (value.isNumber() || value.isBoolean()) ? value : "\"\(value)\""
                } else if input.flTag == 2003 {
                    return "\"\""
                }
            }
        } else if let select = inputControl as? NSPopUpButton {
            if select.flTag == 2002 {
                let layer = self.mappingObjectLayer[select]!.0
                return select.indexOfSelectedItem == 0 ? "value-\(layer)" : "object-\(layer)"
            } else if type == .Boolean {
                return select.indexOfSelectedItem == 0 ? "true" : "false"
            } else if type == .Number {
                if let enumNode = self.mappingEnumNode, select.indexOfSelectedItem < enumNode.children.count,
                   let value = enumNode.children[select.indexOfSelectedItem].value?.value {
                    return value
                }
            }
        }
        return nil
    }
    
    private func onObjectSelectTypesClick(byManual: Bool, jsonObject: [String : Any]?,
                                          _ select: NSPopUpButton, _ inputValue: FLTextField,
                                          _ btnRemoveItem: NSButton, _ currDiv: NSView) {
        if select.indexOfSelectedItem == 1 && inputValue.isEnabled {
            self.addObjectLayerNumber(select)
            btnRemoveItem.isHidden = true
            
            let node = self.currYoga.getNode(by: inputValue)
            node?.height = 26
            inputValue.flText = " ==== scope level: \(self.objectRelativeRoot) ===="
            inputValue.isEnabled = false
            // check insert or append inputs
            if byManual {
                self.findObjectInsertIndex(select, addForLayer: nil)
            }
            // add object inputs
            self.addSuggestInputsControls(.Object, [], false, currDiv, valueJsonObject: jsonObject ?? [:])
            if byManual {
                self.currYoga.apply()
                self.scrollEditor.scrollToBottom(forVertical: true)
            }
        } else if !inputValue.isEnabled {
            btnRemoveItem.isHidden = false
            let node = self.currYoga.getNode(by: inputValue)
            node?.height = 36
            inputValue.flText = ""
            inputValue.isEnabled = true
            var removeSelects = [NSPopUpButton]()
            // remove div nodes
            for (key, info) in self.mappingObjectLayer {
                if key == select {
                    self.objectRelativeRoot = info.0
                } else {
                    // curr item div == sub object superview
                    if currDiv == info.3 {
                        self.removeNestObjectDivs(by: currDiv, removeSelects: &removeSelects)
                    }
                }
            }
            // remove sub controls
            for key in removeSelects {
                self.mappingObjectLayer.removeValue(forKey: key)
                var inputIndex: Int = 0
                for i in 0..<self.customInputControls.count {
                    let item = self.customInputControls[i]
                    if item == key, item.flTag == 2002 {
                        inputIndex = i
                        break
                    }
                }
                for _ in 0..<3 {
                    self.customInputControls.remove(at: inputIndex-1)
                }
            }
            self.currYoga.apply()
        }
    }
    
    private func removeNestObjectDivs(by superView: NSView, removeSelects: inout [NSPopUpButton]) {
        for (key, info) in self.mappingObjectLayer {
            if superView == info.3 {
                self.removeNestObjectDivs(by: info.1, removeSelects: &removeSelects)
                self.currYoga.removeNode(by: info.1)
                removeSelects.append(key)
            }
        }
    }
}

// REMARK: ConfigPrivateParams ViewController

class ConfigPrivateParamsViewController : NSViewController, PropsStatesContext {
    let appContext = HermesContext()
    let yoga = FLContext()
    var properties: MainContext! = nil
    // controls
    let inputSearch = FLInputField()
    let chkDisableParams = FLCheckbox()
    let btnCancel = NSButton()
    let btnSave = NSButton()
    let btnReset = NSButton()
    let btnAttach = NSButton()
    var vcSearchResult: SearchResultViewController! = nil
    var compEditParams: EditPrivateParamsComponent! = nil
    // states
    let localStates = StatesStore()
    var savedParams = [String : AstNode]()
    var forAfter = false
    @PropState
    var searchDataSource: [AstNode] = []
    @PropState
    var selectedSearchItem: AstNode? = nil
    
    // init
    convenience init(_ properties: MainContext, forAfter: Bool) {
        self.init()
        self.bindStates()
        self.properties = properties
        self.forAfter = forAfter
    }
    
    override func loadView() {
        self.view = NSView(frame: CGRect(x: 0, y: 0, width: 720, height: 720))
    }
       
    override func viewDidAppear() {
        if let window = self.view.window {
            window.title = "Global Params Settings (\(self.forAfter ? "After Join" : "Before Join"))"
        }
        if self.compEditParams != nil {
            self.compEditParams.viewDidAppear()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataBinding()
        self.layoutViews()
    }
    
    func dataBinding() {
        self.inputSearch.rac_valueDidChanged.subscribeNext {[weak self] input in
            if let self = self {
                if input.count > 2 {
                    let dataSource = self.appContext.privateParams.fuzzySearchCppPrivateParams(input)
                    self.$searchDataSource.update(value: dataSource, self.localStates)
                } else if input.count == 0 {
                    let dataSource = self.appContext.privateParams.fuzzySearchCppPrivateParams(nil)
                    self.$searchDataSource.update(value: dataSource, self.localStates)
                }
            }
        }
        // buttons
        
        self.btnCancel.rac_eventTouch.subscribeNext {[weak self] _ in
            if let self = self {
                self.properties.exitPrivateParamsSetting(hasSave: false)
            }
        }

        self.btnSave.rac_eventTouch.subscribeNext {[weak self] _ in
            if let self = self {
                // 分开保存用AstNode即可，最终生成一条Instruction
//                self.properties.exitPrivateParamsSetting(hasSave: true)
            }
        }

        self.btnAttach.rac_eventTouch.subscribeNext {[weak self] _ in
            if let self = self {

            }
        }
        // load all private-params
        let searchResult = self.appContext.privateParams.fuzzySearchCppPrivateParams(nil)
        self.$searchDataSource.update(value: searchResult, self.localStates)
    }
    
    func layoutViews() {
        self.view.bindAsRoot(to: yoga)
        let headerPart = NSView()
        headerPart.bind(to: yoga, self.view) { it in
            it.direction = .row
            it.height = 38
            it.alignItems = .center
        }
        let bodyPart = NSView()
        bodyPart.bind(to: yoga, self.view) { it in
            it.direction = .row
            it.flexGrow = 1
            it.marginBottom = 4
            it.paddingHorizontal = 4
        }
        
        // search input
        self.inputSearch.bind(to: yoga, headerPart) { it in
            it.width = 220
            it.height = 24
            it.marginLeft = 8
        }
        self.inputSearch.font = NSFont.systemFont(ofSize: 14)
        
        self.chkDisableParams.bind(to: yoga, headerPart) { it in
            it.width = 120
            it.height = 20
            it.marginLeft = 4
        }
        self.chkDisableParams.flTitle = "Disable Params"
        self.chkDisableParams.flTitleColor = .white
        self.chkDisableParams.flBackgroundColor = .gray
        self.chkDisableParams.flCornerRadius = 4

        // buttons div
        let buttonsDiv = NSView()
        buttonsDiv.bind(to: yoga, headerPart) { it in
            it.direction = .rowReverse
            it.alignItems = .center
            it.right = 8
            it.position = .absolute
        }
        
        self.btnAttach.bind(to: yoga, buttonsDiv) { it in
            it.width = 55
            it.height = 26
            it.marginLeft = 6
        }
        self.btnAttach.flTitle = "Attach"
        self.btnAttach.flTitleColor = .darkGray
        self.btnAttach.flBackgroundColor = .lightGray
        self.btnAttach.flCornerRadius = 4
        self.btnAttach.isEnabled = false
        
        self.btnReset.bind(to: yoga, buttonsDiv) { it in
            it.width = 55
            it.height = 26
            it.marginLeft = 6
        }
        self.btnReset.flTitle = "Reset"
        self.btnReset.flTitleColor = .darkGray
        self.btnReset.flBackgroundColor = .lightGray
        self.btnReset.flCornerRadius = 4
        self.btnReset.isEnabled = false

        self.btnSave.bind(to: yoga, buttonsDiv) { it in
            it.width = 55
            it.height = 26
            it.marginLeft = 6
        }
        self.btnSave.flTitle = "Save"
        self.btnSave.flTitleColor = .white
        self.btnSave.flBackgroundColor = .orange
        self.btnSave.flCornerRadius = 4

        self.btnCancel.bind(to: yoga, buttonsDiv) { it in
            it.width = 55
            it.height = 26
            it.marginLeft = 6
        }
        self.btnCancel.flTitle = "Cancel"
        self.btnCancel.flTitleColor = .darkGray
        self.btnCancel.flBackgroundColor = .lightGray
        self.btnCancel.flCornerRadius = 4
        
        // search result viewcontroller
        self.vcSearchResult = SearchResultViewController(self, presentSize: nil)
        self.vcSearchResult.layoutViews(yoga: yoga, bodyPart)
        // param items table
        self.compEditParams = EditPrivateParamsComponent(self)
        self.compEditParams.layoutViews(yoga: yoga, bodyPart)
        // value input component
        
        yoga.apply()
    }
}
